Skip to content

Z01-04 前端常用-工具方法

[TOC]

基本

判断对象类型

  • isObject(value)返回:boolean,判断value是否为一个对象(对象,数组,函数)

定义函数

js
/**
 * 判断对象类型
 * @param {any} val 需要判断类型的值
 * @returns {boolean} 是否为对象类型
 */
function isObject(val) {
  const type = typeof val
  return val !== null && (type === 'object' || type === 'function')
}

测试函数

js
// 测试
// true: array, object, set, map
// false: string, number,boolean, undefined, null
console.log(isObject('tom'))
console.log(isObject(123))
console.log(isObject(['a', 'b', 'c']))
console.log(isObject({ name: 'jack' }))
console.log(isObject(true))
console.log(isObject(undefined))
console.log(isObject(null))
console.log(isObject(new Set()))
console.log(isObject(new Map()))

格式化

格式化-时间

  • formatTime(timeStamp, format)返回:format,格式化时间

定义函数

js
/**
 * @description 格式化时间
 * @param {number} timeStamp 时间戳
 * @param {string} format 输出的时间格式
 * @returns {string} format 格式化后的时间
 */
function formatTime(timeStamp, format) {
  const time = new Date(timeStamp)
  // 正则和值匹配
  const oDate = {
    'y+': time.getFullYear(),
    'M+': time.getMonth() + 1,
    'd+': time.getDate(),
    'h+': time.getHours(),
    'm+': time.getMinutes(),
    's+': time.getSeconds()
  }
  // for循环替换
  for (const key in oDate) {
    const keyRe = new RegExp(key)
    if (keyRe.test(format)) {
      const value = (oDate[key] + '').padStart(2, '0')
      format = format.replace(keyRe, value)
    }
  }
  return format
}

调用函数

js
// 测试
console.log(formatTime(1694589797757, 'yyyy-MM-dd hh:mm:ss')) // 2023-09-13 15:23:17
console.log(formatTime(1694589797757, 'yyyy/MM/dd hh:mm:ss')) // 2023/09/13 15:23:17
console.log(formatTime(1694589797757, 'hh:mm:ss')) // 15:23:17
console.log(formatTime(1694589797757, 'yyyy/MM/dd')) // 2023/09/13

格式化-歌词

  • formatLyric(lyric)返回:aLyricParsed,格式化歌词

定义函数

js
/**
 * @description 解析歌词:将字符格式的歌词解析成对象格式
 * @param {string} lyric
 * @returns {array} aLyricParsed 格式如下:[{time: 2313, content: '歌词'},...]
 */
function parseLyric(lyric) {
  const aLyricParsed = []
  // 通过\n分割字符
  const aLyric = lyric.split('\n')
  // 遍历数组
  const re = /\[(\d{2}):(\d{2})\.(\d{2,3})\]/
  for (const lyricLine of aLyric) {
    // 去除空元素
    if (!lyricLine) continue  // 不能写成 return
    // 获取歌词时间
    const aTime = lyricLine.match(re)
    const milliSecond = aTime[3].length === 2 ? aTime[3] * 10 : aTime[3] * 1
    const time = aTime[1] * 60 * 1000 + aTime[2] * 1000 + milliSecond
    // 获取歌词内容
    const content = lyricLine.replace(re, '').trim()
    aLyricParsed.push({ time, content })
  }
  return aLyricParsed
}

问题:将 if (!lyricLine) continue 写成了 if (!lyricLine) return,导致for循环后续代码没有执行

调用函数

js
const lyric =
  '[00:00.000] 作词 : 许嵩\n[00:01.000] 作曲 : 许嵩\n[00:02.000] 编曲 : 许嵩\n[00:03.000] 制作人 : 许嵩\n[00:22.240]天空好想下雨\n[00:24.380]我好想住你隔壁\n[00:26.810]傻站在你家楼下\n[00:29.500]抬起头数乌云\n[00:31.160]如果场景里出现一架钢琴\n[00:33.640]我会唱歌给你听\n[00:35.900]哪怕好多盆水往下淋\n[00:41.060]夏天快要过去\n[00:43.340]请你少买冰淇淋\n[00:45.680]天凉就别穿短裙\n[00:47.830]别再那么淘气\n[00:50.060]如果有时不那么开心\n[00:52.470]我愿意将格洛米借给你\n[00:55.020]你其实明白我心意\n[00:58.290]为你唱这首歌没有什么风格\n[01:02.976]它仅仅代表着我想给你快乐\n[01:07.840]为你解冻冰河为你做一只扑火的飞蛾\n[01:12.998]没有什么事情是不值得\n[01:17.489]为你唱这首歌没有什么风格\n[01:21.998]它仅仅代表着我希望你快乐\n[01:26.688]为你辗转反侧为你放弃世界有何不可\n[01:32.328]夏末秋凉里带一点温热有换季的颜色\n[01:41.040]\n[01:57.908]天空好想下雨\n[01:59.378]我好想住你隔壁\n[02:02.296]傻站在你家楼下\n[02:03.846]抬起头数乌云\n[02:06.183]如果场景里出现一架钢琴\n[02:08.875]我会唱歌给你听\n[02:10.974]哪怕好多盆水往下淋\n[02:15.325]夏天快要过去\n[02:18.345]请你少买冰淇淋\n[02:21.484]天凉就别穿短裙\n[02:22.914]别再那么淘气\n[02:25.185]如果有时不那么开心\n[02:27.625]我愿意将格洛米借给你\n[02:30.015]你其实明白我心意\n[02:33.327]为你唱这首歌没有什么风格\n[02:37.976]它仅仅代表着我想给你快乐\n[02:42.835]为你解冻冰河为你做一只扑火的飞蛾\n[02:48.406]没有什么事情是不值得\n[02:52.416]为你唱这首歌没有什么风格\n[02:57.077]它仅仅代表着我希望你快乐\n[03:01.993]为你辗转反侧为你放弃世界有何不可\n[03:07.494]夏末秋凉里带一点温热\n[03:11.536]\n[03:20.924]为你解冻冰河为你做一只扑火的飞蛾\n[03:26.615]没有什么事情是不值得\n[03:30.525]为你唱这首歌没有什么风格\n[03:35.196]它仅仅代表着我希望你快乐\n[03:39.946]为你辗转反侧为你放弃世界有何不可\n[03:45.644]夏末秋凉里带一点温热有换季的颜色\n'

console.log(parseLyric(lyric))

手写

call

  • mrcall(mrthis, ...args)返回:void,call绑定this方法

定义函数

js
/**
 * call绑定this方法
 * @param {object} mrthis this对象
 * @param {...any} args 参数
 */
Function.prototype.mrcall = function (mrthis, ...args) {
  mrthis = mrthis === undefined || mrthis === null ? window : Object(mrthis)

  // mrthis.fn = this;
  Object.defineProperty(mrthis, 'fn', {
    configurable: true,
    value: this
  })

  mrthis.fn(...args)

  delete mrthis.fn
}

测试

js
function foo(name, age) {
  console.log(this, name, age)
}
foo.mrcall({ name: 'Tom' }, 'Tom', 18)

apply

  • mrapply(mrthis, args)返回:void,apply绑定this方法

定义函数

js
/**
 * apply绑定this方法
 * @param {object} mrthis this对象
 * @param {any} args 参数数组
 */
Function.prototype.mrapply = function (mrthis, args) {
  mrthis = mrthis === undefined || mrthis === null ? window : Object(mrthis)

  Object.defineProperty(mrthis, 'fn', {
    configurable: true,
    value: this
  })

  mrthis.fn(...args)

  delete mrthis.fn
}

测试

js
function foo(name, age) {
  console.log(this, name, age)
}
foo.mrapply({ name: 'Jack' }, ['Jack', 20])

bind

  • mrbind(mrthis, ...args)返回:bindFn,bind绑定this方法

定义函数

js
/**
 * bind绑定this方法
 * @param {object} mrthis this对象
 * @param  {...any} args 参数数组
 * @returns 
 */
Function.prototype.mrbind = function (mrthis, ...args) {
  return (...moreArgs) => {
    mrthis = mrthis === undefined || mrthis === null ? window : Object(mrthis)
    Object.defineProperty(mrthis, 'fn', {
      configurable: true,
      value: this
    })

    const allArgs = [...args, ...moreArgs]
    mrthis.fn(...allArgs)

    delete mrthis.fn
  }
}

测试

js
function foo(name, age, height) {
  console.log(this, name, age, height)
}
const newFoo = foo.mrbind({ name: 'Tom' }, 'Tom', 18)
newFoo(1.88) // => {name: 'Tom', fn: ƒ} 'Tom' 18 1.88

防抖

  • mrDebounce(fn, delay, immediate)返回:_fnDebounce,防抖函数

定义函数

js
/**
 * 防抖函数
 *  1. 基本实现
 *  2. 优化:参数和this指向
 *  3. 优化:取消功能
 *  4. 优化:第一次立即执行
 *  5. 优化:返回值-利用Promise实现异步返回值
 * @param {function} fn 需要防抖的回调函数
 * @param {number} delay 延迟时间
 * @param {boolean} immediate 是否开启第一次立即执行
 * @returns {function} _fnDebounce
 */
function mrDebounce(fn, delay, immediate = false) {
  let timer = null
  let isFirst = true

  // 1. 基本实现
  const _fnDebounce = function (...args) {
    // 5. 返回值
    return new Promise((resolve, reject) => {
      try {
        let res = null
        if (timer) clearTimeout(timer)

        // 3. 第一次立即执行
        if (immediate && isFirst) {
          res = fn.apply(this, args)
          resolve(res)
          isFirst = false
          return
        }

        // 延迟执行
        timer = setTimeout(() => {
          // 2. 参数和this指向
          res = fn.apply(this, args)
          resolve(res)
          timer = null
          isFirst = true
        }, delay)
      } catch (err) {
        reject(err)
      }
    })
  }

  // 4. 取消功能
  _fnDebounce.cancel = function () {
    if (timer) clearTimeout(timer)
    timer = null
    isFirst = true
  }
  return _fnDebounce
}

调用函数

html
    <input type="text" class="input" />
    <button class="cancel">取消</button>
js
// 测试
const eInput = document.querySelector('.input')
const eCancelBtn = document.querySelector('.cancel')

// 基本实现
let count = 0
const fnDebounce = mrDebounce(function (name, age, gender) {
  // console.log(`发送了 ${++count} 次网络请求:`, this.value, e)
  console.log(`发送了 ${++count} 次网络请求:`, name, age, gender)
  return '哈哈哈'
}, 2000)
eInput.oninput = fnDebounce

// 取消功能
eCancelBtn.onclick = fnDebounce.cancel

// 返回值
fnDebounce('tom', 18, 'male').then((res) => {
  console.log('res: ', res)
})

节流

  • mrThrottle(fn, interval, {leading, trailing})返回:_throttleFn,节流函数

定义函数

js
/**
 * 节流函数
 * 1. 基本实现
 * 2. 优化:绑定参数和this
 * 3. 优化:控制立即执行
 * 4. 优化:控制最后一次执行
 * 5. 优化:取消功能
 * 6. 优化:返回值
 * @param {function} fn 被节流的回调函数
 * @param {number} interval 间隔时间
 * @param {object} {leading, trailing} leading:是否开启立即执行, trailing:是否执行最后一次
 * @returns {_throttleFn} 节流后的函数
 **/
function mrThrottle(fn, interval, { leading = true, trailing = false } = {}) {
  let startTime = 0
  let timer = null

  // 1. 基本实现
  const _throttleFn = function (...args) {
    return new Promise((resolve, reject) => {
      try {
        const nowTime = new Date().getTime()

        // 3. 控制立即执行
        if (!leading && startTime === 0) {
          startTime = nowTime
        }

        const waitTime = interval - (nowTime - startTime)
        if (waitTime <= 0) {
          if (timer) clearTimeout(timer)
          // 2. 绑定参数和this
          const res = fn.apply(this, args)
          resolve(res)
          startTime = nowTime
          timer = null
          return
        }

        // 4. 控制最后一次执行
        if (trailing && !timer) {
          timer = setTimeout(function () {
            const res = fn.apply(this, args)
            resolve(res)
            startTime = new Date().getTime()
            timer = null
          }, waitTime)
        }
      } catch (err) {
        reject(err)
      }
    })
  }

  // 5. 取消功能
  _throttleFn.cancel = function () {
    if (timer) clearTimeout(timer)
  }

  return _throttleFn
}

测试

js
// 测试
const eInput = document.querySelector('.input')
const eCancelBtn = document.querySelector('.cancel')

let count = 0
const throttleFn = mrThrottle(
  function (e) {
    console.log(`发送了 ${++count} 次网络请求: `, this.value, e)
    return '哈哈~'
  },
  3000,
  { leading: false, trailing: true }
)

// 测试:节流
eInput.oninput = throttleFn

// 测试:取消功能
eCancelBtn.onclick = throttleFn.cancel

// 测试:返回值
throttleFn('tom').then((res) => {
  console.log('res: ', res)
})

深拷贝

  • deepCopy(originValue, map)返回:newObj,深拷贝

定义函数

js
/**
 * 深拷贝
 * 1. 基本实现
 * 2. 优化:区分数组和对象
 * 3. 优化:处理其他类型-set
 * 4. 优化:处理其他类型-map
 * 5. 优化:处理其他类型-function
 * 6. 优化:处理其他类型-symbol为值
 * 7. 优化:处理其他类型-symbol为key
 * 8. 优化:处理循环引用
 * @param {object} originValue 原始值
 * @param {map} map 保存新对象的map
 * @returns {object} newObj 深拷贝后的对象
 */
function deepCopy(originValue, map = new WeakMap()) {
  // 1. 基本实现
  // 6. 处理其他类型-symbol为值
  if (Object.prototype.toString.call(originValue) === '[object Symbol]') {
    const newSymbol = Symbol(originValue.description)
    return newSymbol
  }

  // 如果不是对象类型,直接返回原始值
  if (!isObject(originValue)) {
    return originValue
  }

  // 3. 处理其他类型-set
  if (Object.prototype.toString.call(originValue) === '[object Set]') {
    const newSet = new Set()
    for (const setItem of originValue) {
      newSet.add(deepCopy(setItem, map))
    }
    return newSet
  }

  // 4. 处理其他类型-map
  if (Object.prototype.toString.call(originValue) === '[object Map]') {
    const newMap = new Map()
    for (const [mapKey, mapValue] of originValue) {
      newMap.set(mapKey, mapValue)
    }
    return newMap
  }

  // 5. 处理其他类型-function
  if (Object.prototype.toString.call(originValue) === '[object Function]') {
    return originValue
  }

  // 如果是对象类型,则创建一个新对象
  // 8. 处理循环引用
  if (map.get(originValue)) {
    return map.get(originValue)
  }
  // 2. 区分数组和对象
  const newObj = Array.isArray(originValue) ? [] : {}
  map.set(originValue, newObj)
  for (const key in originValue) {
    newObj[key] = deepCopy(originValue[key], map)
  }

  // 7. 处理其他类型-symbol为key
  const symbolKeys = Object.getOwnPropertySymbols(originValue)
  for (const symbolKey of symbolKeys) {
    console.log(symbolKey)
    newObj[Symbol(symbolKey.description)] = deepCopy(originValue[symbolKey], map)
  }
  return newObj
}

测试

js
// 测试
const obj = {
  name: 'tom',
  age: 18,
  friend: {
    name: 'jack',
    gender: 'male',
    address: {
      country: 'China',
      city: 'Shanghai'
    }
  },
  hobbies: ['football', 'tennis'],
  set: new Set([111, 222, 333, { name: 'setItem' }]),
  map: new Map([
    ['刘备', { country: '蜀', nowLocation: '四川' }],
    ['曹操', { country: '魏', nowLocation: '北方' }],
    ['孙权', { country: '吴', nowLocation: '江南' }]
  ]),
  s1: Symbol('s1'),
  [Symbol('s2')]: 's2'
}
obj['self'] = obj
const newObj = deepCopy(obj)
obj.friend.name = '张飞'
console.log(newObj)
console.log(newObj.friend.name)

console.log(deepCopy('name'))

事件总线

  • MrEventBus返回:,事件总线
    • on(eName, eFn)返回:,监听事件
    • emit(eName, ...args)返回:,触发事件
    • off(eName, eFn)返回:,取消事件

定义类

js
/**
 * 事件总线
 * 1. 基本实现
 * 2. 优化:绑定参数
 * 3. 优化:off方法
 */
class MrEventBus {
  constructor() {
    this.eMap = {}
  }
  // 监听事件
  on(eName, eFn) {
    let aFn = this.eMap[eName]
    if (!aFn) {
      aFn = []
      this.eMap[eName] = aFn
    }

    aFn.push(eFn)
  }

  // 触发事件
  emit(eName, ...args) {
    const aFn = this.eMap[eName]
    if (!aFn) return
    aFn.forEach((fn, i) => {
      fn(...args)
    })
  }

  // 取消事件
  off(eName, eFn) {
    const aFn = this.eMap[eName]
    if (!aFn) return
    aFn.forEach((fn, i) => {
      if (fn === eFn) {
        aFn.splice(i, 1)
        return
      }
    })
    // 如果aFn清空了
    if (aFn.length === 0) {
      delete this.eMap[eName]
    }
  }
}

测试

html
<button class="btn">触发</button>
js
// 测试
const elBtn = document.querySelector('.btn')
const eb = new MrEventBus()
const hdlClick = function (name, age) {
  console.log('监听点击事件hdlClick~', name, age)
}
const hdlClick2 = function (name, age) {
  console.log('监听点击事件hdlClick2~', name, age)
}
// 1. 监听事件
eb.on('mrclick', hdlClick)
eb.on('mrclick', hdlClick2)

// 2. 触发事件
// eb.emit('mrclick', 'tom', 18)

// 3. 取消事件:一开始可以触发2个事件,3秒后只能触发一个事件
setTimeout(function () {
  eb.off('mrclick', hdlClick)
}, 3000)
elBtn.onclick = function () {
  eb.emit('mrclick', 'tom', 18)
}

Promise

  • MrPromise返回:,手写Promise
    • 实例方法
    • then(onFulfilled, onRejected)返回:,then方法
    • catch(onRejected)返回:,catch方法
    • finally(onFinally)返回:,finally方法
    • 类方法
    • resolve(value)返回:
    • reject(reason)返回:
    • all(promises)返回:
    • allSettled(promises)返回:
    • race(promises)返回:
    • any(promises)返回:

定义类

js
/**
 * 手写Promise
 * 1. 基本实现
 * 2. 类方法-resolve
 * 3. 类方法-reject
 * 4. 类方法-all
 * 5. 类方法-allSettled
 * 6. 类方法-race
 * 7. 类方法-any
 * 8. 异步延迟调用
 * 9. 链式调用
 * 10. catch方法
 * 11. finally方法
 */
/* 工具函数-封装try...catch函数 */
function runFunctionWithCatchError(fn, value, resolve, reject) {
  try {
    resolve(fn(value))
  } catch (err) {
    reject(err)
  }
}

// Promise状态
const PROMISE_STATUS_PENDING = 'pending'
const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_REJECTED = 'rejected'

class MrPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING
    this.value = undefined
    this.reason = undefined
    this.onFulfilledFns = []
    this.onRejectedFns = []

    const resolve = (value) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return
          this.status = PROMISE_STATUS_FULFILLED
          this.value = value
          for (const fn of this.onFulfilledFns) {
            fn(this.value)
          }
        })
      }
    }

    const reject = (reason) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        queueMicrotask(() => {
          if (this.status !== PROMISE_STATUS_PENDING) return
          this.status = PROMISE_STATUS_REJECTED
          this.reason = reason
          for (const fn of this.onRejectedFns) {
            fn(this.reason)
          }
        })
      }
    }

    try {
      executor(resolve, reject)
    } catch (err) {
      reject(err)
    }
  }

  then(onFulfilled, onRejected) {
    // 判断onFulfilled、onRejected回调函数是否存在
    onRejected =
      onRejected ||
      ((err) => {
        throw err
      })
    onFulfilled = onFulfilled || ((res) => res)

    return new MrPromise((resolve, reject) => {
      // console.log('then status: ', this.status)
      if (this.status === PROMISE_STATUS_FULFILLED) {
        runFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
      }
      if (this.status === PROMISE_STATUS_REJECTED) {
        runFunctionWithCatchError(onRejected, this.reason, resolve, reject)
      }

      if (this.status === PROMISE_STATUS_PENDING) {
        this.onFulfilledFns.push(() => {
          runFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
        })

        this.onRejectedFns.push(() => {
          runFunctionWithCatchError(onRejected, this.reason, resolve, reject)
        })
      }
    })
  }

  catch(onRejected) {
    return this.then(undefined, onRejected)
  }

  finally(onFinally) {
    this.then(
      () => {
        onFinally()
      },
      () => {
        onFinally()
      }
    )
  }

  static resolve(value) {
    return new Promise((resolve) => resolve(value))
  }

  static reject(reason) {
    return new Promise((resolve, reject) => reject(reason))
  }

  static all(promises) {
    return new Promise((resolve, reject) => {
      const values = []
      promises.forEach((promise) => {
        promise.then(
          (res) => {
            values.push(res)
            if (values.length === promises.length) {
              resolve(values)
            }
          },
          (err) => {
            reject(err)
          }
        )
      })
    })
  }

  static allSettled(promises) {
    return new Promise((resolve, reject) => {
      const results = []
      promises.forEach((promise) => {
        promise.then(
          (res) => {
            results.push({ status: 'fulfilled', value: res })
            if (results.length === promises.length) {
              resolve(results)
            }
          },
          (err) => {
            results.push({ status: 'rejected', reason: err })
            if (results.length === promises.length) {
              resolve(results)
            }
          }
        )
      })
    })
  }

  static race(promises) {
    return new Promise((resolve, reject) => {
      promises.forEach((promise) => {
        promise.then(
          (res) => {
            resolve(res)
          },
          (err) => {
            reject(err)
          }
        )
      })
    })
  }

  static any(promises) {
    return new Promise((resolve, reject) => {
      const reasons = []
      promises.forEach((promise) => {
        promise.then(
          (res) => {
            resolve(res)
          },
          (err) => {
            reasons.push(err)
            if (reasons.length === promises.length) {
              reject(new AggregateError(err))
            }
          }
        )
      })
    })
  }
}

测试

js
// 测试
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('p1~')
  }, 3000)
})
const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('p2~')
  }, 5000)
})
const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('p3~')
  }, 3000)
})

const p = new MrPromise((resolve, reject) => {
  // throw new Error('抛出异常')

  resolve('aaa')
  // reject('111')

  // setTimeout(() => {
  //   // resolve('aaa')
  //   reject('111')
  // }, 1000)
})

// - 类方法-any
Promise.any([p1, p2, p3]).then(
  (res) => {
    console.log('any res: ', res)
  },
  (err) => {
    console.log('any err: ', err)
  }
)

// // - 类方法-race
// Promise.race([p1, p2, p3]).then(
//   (res) => {
//     console.log('race res: ', res)
//   },
//   (err) => {
//     console.log('race err: ', err)
//   }
// )

// // - 类方法-allSettled
// Promise.allSettled([p1, p2, p3]).then((res) => {
//   console.log('allSettled: ', res)
// })

// // - 类方法-all
// Promise.all([p1, p2, p3]).then(
//   (res) => {
//     console.log('all res: ', res)
//   },
//   (err) => {
//     console.log('all err: ', err)
//   }
// )

// // - 类方法-reject
// Promise.reject('222').catch((err) => {
//   console.log(err)
// })

// // - 类方法-resolve
// Promise.resolve('1111').then((res) => {
//   console.log(res)
// })

// p.then(
//   (res) => {
//     console.log('res: ', res)
//   },
//   (err) => {
//     console.log('err: ', err)
//   }
// )

// // - 异步延迟调用
// setTimeout(() => {
//   p.then(
//     (res) => {
//       console.log('异步延时调用 res: ', res)
//     },
//     (err) => {
//       console.log('异步延时调用 err: ', err)
//     }
//   )
// }, 2000)

// // - 链式调用
// p.then(
//   (res) => {
//     console.log('链式调用 res1: ', res)
//     return 'bbb'
//   },
//   (err) => {
//     console.log('链式调用 err1: ', err)
//     return '222'
//   }
// ).then(
//   (res) => {
//     console.log('链式调用 res2: ', res)
//     return 'ccc'
//   },
//   (err) => {
//     console.log('链式调用 err2: ', err)
//     return '333'
//   }
// )

// // - catch
// p.then((res) => {
//   console.log('then res: ', res)
// }).catch((err) => {
//   console.log('catch err: ', err)
// })

// // - finally
// p.then((res) => {
//   console.log('then res: ', res)
// })
//   .catch((err) => {
//     console.log('catch err: ', err)
//   })
//   .finally(() => {
//     console.log('finally~')
//   })

// p.then(
//   (res) => {
//     console.log('res: ', res)
//   },
//   (err) => {
//     console.log('err: ', err)
//   }
// )
// p.then(
//   (res) => {
//     console.log('res2: ', res)
//   },
//   (err) => {
//     console.log('err2: ', err)
//   }
// )

响应式【】

  • 返回:

网络请求

axios封装【】

  • 返回: